﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Resources;
using System.Text.RegularExpressions;

namespace WillsLLC.DbGen.Text.Inflection
{
    /// <summary>
    /// A utility class for implementations of predicates defined in http://www.csse.monash.edu.au/~damian/papers/HTML/Plurals.html and other helper methods.
    /// </summary>
    internal static class InflectionUtility
    {
        #region [ Resource Collections ]
        private static ResourceManager ResourceManager
        {
            get { return new ResourceManager("WillsLLC.DbGen.Text.Inflection.InflectionResources", typeof(InflectionUtility).Assembly); }
        }

        private static Dictionary<String, String> GetDictionaryFromResource(String resourceName)
        {
            String resource = ResourceManager.GetString(resourceName);
            String[] resources = resource.Split(new String[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Where(line => !line.StartsWith("//")).ToArray();
            String[][] pairs = resources.Select(suffix => suffix.Split(' ')).ToArray();
            return pairs.ToDictionary(pair => pair.Length == 2 ? pair[1] : pair[0], pair => pair[0]);
        }

        private static List<String> GetListFromResource(String resourceName)
        {
            String resource = ResourceManager.GetString(resourceName);
            String[] resources = resource.Split(new String[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Where(line => !line.StartsWith("//")).ToArray();
            return resources.ToList();
        }

        private static Dictionary<String, String> _singularSuffixes;
        internal static Dictionary<String, String> SingularSuffixes
        {
            get
            {
                if (InflectionUtility._singularSuffixes == null)
                {
                    InflectionUtility._singularSuffixes = InflectionUtility.GetDictionaryFromResource("SingularSuffixes");
                }
                return InflectionUtility._singularSuffixes;
            }
        }

        private static Dictionary<String, String> _pluralSuffixes;
        internal static Dictionary<String, String> PluralSuffixes
        {
            get
            {
                if (InflectionUtility._pluralSuffixes == null)
                {
                    InflectionUtility._pluralSuffixes = InflectionUtility.GetDictionaryFromResource("PluralSuffixes");
                }
                return InflectionUtility._pluralSuffixes;
            }
        }

        private static List<WordCategorization> _wordCategorizations;
        internal static List<WordCategorization> WordCategorizations
        {
            get
            {
                if (InflectionUtility._wordCategorizations == null)
                {
                    InflectionUtility._wordCategorizations = InflectionUtility.GetListFromResource("WordCategorizations").ConvertAll(resource => new WordCategorization(resource));
                }
                return InflectionUtility._wordCategorizations;
            }
        }

        private static List<String> _doesNotInflectPlural;
        internal static List<String> DoesNotInflectPlural
        {
            get
            {
                if (InflectionUtility._doesNotInflectPlural == null)
                {
                    InflectionUtility._doesNotInflectPlural = InflectionUtility.GetListFromResource("DoesNotInflectPlural");
                }
                return InflectionUtility._doesNotInflectPlural;
            }
        }

        private static List<String> _prepositions;
        internal static List<String> Prepositions
        {
            get
            {
                if (InflectionUtility._prepositions == null)
                {
                    InflectionUtility._prepositions = InflectionUtility.GetListFromResource("Prepositions");
                }
                return InflectionUtility._prepositions;
            }
        }

        private static List<IrregularPlural> _irregularPlurals;
        internal static List<IrregularPlural> IrregularPlurals
        {
            get
            {
                if (InflectionUtility._irregularPlurals == null)
                {
                    InflectionUtility._irregularPlurals = InflectionUtility.GetListFromResource("IrregularPlurals").ConvertAll(resource => new IrregularPlural(resource));
                }
                return InflectionUtility._irregularPlurals;
            }
        }

        private static List<Pronoun> _pronouns;
        internal static List<Pronoun> Pronouns
        {
            get
            {
                if (InflectionUtility._pronouns == null)
                {
                    InflectionUtility._pronouns = InflectionUtility.GetListFromResource("Pronouns").ConvertAll(resource => new Pronoun(resource));
                }
                return InflectionUtility._pronouns;
            }
        }

        private static List<IrregularInflection> _irregularInflections;
        internal static List<IrregularInflection> IrregularInflections
        {
            get
            {
                if (InflectionUtility._irregularInflections == null)
                {
                    InflectionUtility._irregularInflections = InflectionUtility.GetListFromResource("IrregularInflections").ConvertAll(resource => new IrregularInflection(resource));
                }
                return InflectionUtility._irregularInflections;
            }
        }
        #endregion

        #region [ Regex Patterns ]
        internal const String PATTERN_FORMAT_SUFFIX = @"(?i:)^\w*({0})$";
        internal const String PATTERN_FORMAT_WORD = @"(?i:)^{0}$";
        internal const String PATTERN_FORMAT_PREPOSITIONAL_PHRASE = @"(?i:)^{0} {1}$";
        internal const String PATTERN_PHRASE = @"(?i:)^([\w\s]*)\b(\w+)$";

        internal static Regex GetSuffixPattern(String suffix)
        {
            return new Regex(String.Format(PATTERN_FORMAT_SUFFIX, suffix));
        }

        internal static Regex GetWordPattern(String word)
        {
            return new Regex(String.Format(PATTERN_FORMAT_WORD, word));
        }

        internal static Regex GetPrepositionalPhrasePattern(String prepositionalPhrase, String word)
        {
            return new Regex(String.Format(PATTERN_FORMAT_PREPOSITIONAL_PHRASE, prepositionalPhrase, word));
        }

        internal static Regex GetPhrasePattern()
        {
            return new Regex(PATTERN_PHRASE);
        }
        #endregion

        #region [ Utility ]
        /// <summary>
        /// Returns true if the word being inflected ends with <paramref name="suffix" />; false if it does not.
        /// </summary>
        internal static Boolean EndsWithSuffix(this String word, String suffix)
        {
            return InflectionUtility.GetSuffixPattern(suffix).IsMatch(word);
        }

        /// <summary>
        /// Returns true if the word being inflected belongs to the set of English words whose suffixes inflect from
        /// <paramref name="singularSuffix"/> to <paramref name="pluralSuffix"/> when pluralized.
        /// </summary>
        internal static Boolean IsCategoryMember(this String word, String singularSuffix, String pluralSuffix, Boolean classicalMode)
        {
            return InflectionUtility.WordCategorizations.Where(cat => cat.IsCategoryMember(word, singularSuffix, pluralSuffix, classicalMode)).Count() > 0;
        }

        /// <summary>
        /// Returns the word being inflected after replacing its current suffix (which must be <paramref name="singularSuffix" />) with
        /// <paramref name="pluralSuffix" />.
        /// </summary>
        internal static String Inflect(this String word, String singularSuffix, String pluralSuffix)
        {
            Regex suffixPattern = InflectionUtility.GetSuffixPattern(singularSuffix);            
            if (suffixPattern.IsMatch(word))
            {
                return String.Format("{0}{1}", suffixPattern.Replace(word, (match => match.Groups[1].Value.Length == 0 ? word : match.Value.Replace(match.Groups[1].Value, String.Empty))), pluralSuffix);
            }
            else return null;           
        }

        /// <summary>
        /// Removes the specified suffix from the word being inflected and returns the remaining stem. If the word does not originally end
        /// in the specified suffix, a null reference is returned.
        /// </summary>
        internal static String GetWordStem(this String word, String suffix)
        {
            Regex suffixPattern = InflectionUtility.GetSuffixPattern(suffix);
            Match match = suffixPattern.Match(word);
            if (match.Success) return suffixPattern.Replace(suffix, String.Empty);
            else return null;
        }

        internal static Boolean TryGetCategorizedInflection(this String word, Boolean classicalMode, out String plural, params SingularPluralSuffixPair[] categories)
        {
            foreach (SingularPluralSuffixPair category in categories)
            {
                if (word.IsCategoryMember(category.Pattern, category.PluralSuffix, classicalMode))
                {
                    plural = word.Inflect(category.SingularSuffix, category.PluralSuffix);
                    return true;
                }
            }
            plural = null;
            return false;
        }

        internal static Boolean TryGetSuffixInflection(this String word, out String plural, params SingularPluralSuffixPair[] suffixes)
        {
            foreach (SingularPluralSuffixPair suffix in suffixes)
            {
                if (word.EndsWithSuffix(suffix.Pattern))
                {
                    plural = word.Inflect(suffix.SingularSuffix, suffix.PluralSuffix);
                    return true;
                }
            }
            plural = null;
            return false;
        }
        #endregion
    }
}